﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.Linq.Expressions;

namespace DDD {

    /// <summary>
    /// コンポーネントをアタッチ可能にする抽象クラス
    /// </summary>
    /// <remarks>
    /// この抽象クラスは<see cref="Object3D"/>クラスを継承しコンポーネントをアタッチする機能を追加します。
    /// 空間上の1点に過ぎなかったオブジェクトは、種々のコンポーネントをアタッチすることで
    /// カメラやメッシュなどの機能を持つようになります。
    /// またスコープや各種Enableフラグもこのクラスが担当します（あまりいいデザインではない・・・）。
    /// </remarks>
    public class Attachable : Object3D {

        #region Field
        List<Component> components;
        int order;
        int scope;
        float alpha;
        bool renderingEnabled;
        bool pickingEnabled;
        bool collisionEnabled;
        bool lodEnabled;
        #endregion

        #region Constructor
        /// <summary>
        /// コンストラクター
        /// </summary>
        public Attachable () {
            this.components = new List<Component> ();
            this.order = 0;
            this.scope = -1;
            this.alpha = 1.0f;
            this.renderingEnabled = true;
            this.pickingEnabled = true;
            this.collisionEnabled = true;
            this.lodEnabled = true;
        }
        #endregion


        #region Property

        /// <summary>
        /// コンポーネントの実行順序
        /// </summary>
        /// <remarks>
        /// OnUpdate()関数はこの値が小さいノードから順に実行されます。
        /// デフォルトの優先度は0です。
        /// </remarks>
        public int ExecutionOrder {
            get { return order; }
            set { this.order = value; }
        }

        /// <summary>
        /// スコープ
        /// </summary>
        /// <remarks>
        /// スコープはINT型の整数値で２つのスコープのビットANDの計算結果が０でないとき
        /// 同じスコープにいると見なされます。
        /// これは典型的にはレンダリングやコリジョン検出で特定のグループのみを選択的に実行したいときに使用されます。
        /// デフォルト値は-1、つまり全てのスコープに含まれます。
        /// </remarks>
        public int Scope {
            get { return scope; }
            set { this.scope = value; }
        }

        /// <summary>
        /// ノードの不透明度(α)
        /// </summary>
        /// <remarks>
        /// ノードにはノードαと呼ばれる不透明度を設定可能です。
        /// α=0は透明でα=1は不透明です。
        /// ノードαは単なるプレイスホルダーで実際に半透明で描画されるかどうかはシェーダーの作りに依存します。
        /// </remarks>
        public float Alpha {
            get { return alpha; }
            set {
                var alpha = value;
                if (alpha < 0 || alpha > 1) {
                    throw new ArgumentException ("Alpha must be within [0,1]");
                }
                this.alpha = alpha;
            }
        }



        /// <summary>
        /// レンダリングフラグ
        /// </summary>
        /// <remarks>
        /// レンダリング処理はこのフラグが<dic>true</dic>なノードに対してのみ行われます(シェーダーも実行されません)。
        /// </remarks>
        public bool IsRenderingEnabled {
            get { return renderingEnabled; }
            set { this.renderingEnabled = value; }
        }

        /// <summary>
        /// ピッキングフラグ
        /// </summary>
        /// <remarks>
        /// ピッキング処理はこのフラグが<dic>True</dic>なノードに対してのみ行われます。
        /// </remarks>
        public bool IsPickingEnabled {
            get { return pickingEnabled; }
            set { this.pickingEnabled = value; }
        }

        /// <summary>
        /// コリジョン検出フラグ
        /// </summary>
        /// <remarks>
        /// コリジョン検出はこのフラグがTrueなノードに対してのみ行われます。
        /// </remarks>
        public bool IsCollisionEnabled {
            get { return collisionEnabled; }
            set { this.collisionEnabled = value; }
        }

        /// <summary>
        /// LOD処理フラグ
        /// </summary>
        /// <remarks>
        /// LOD処理はこのフラグがTrueなノードに対してのみ行われます。
        /// このフラグがFalseの時は常に一番詳細度の高いモデルが使用されます。
        /// </remarks>
        public bool IsLODEnabled {
            get { return lodEnabled; }
            set { this.lodEnabled = value; }
        }

        /// <summary>
        /// このノードにアタッチされているコンポーネント数の合計
        /// </summary>
        /// <remarks>
        /// </remarks>
        public int ComponentCount {
            get { return components.Count; }
        }

        /// <summary>
        /// このノードにアタッチされている全てのコンポーネントを列挙する列挙子
        /// </summary>
        public IEnumerable<Component> Components {
            get { return components; }
        }

        /// <summary>
        /// このオブジェクトが参照する全DDDオブジェクトを列挙する列挙子
        /// </summary>
        /// <remarks>
        /// 参照にnullは含まれません。
        /// </remarks>
        public override IEnumerable<Object> References {
            get {
                var objs = base.References.ToList ();
                objs.AddRange (components);
                objs.RemoveAll (x=>x==null);
                return objs;
            }
        }
        #endregion


        #region Method

        /// <summary>
        /// このノードに指定のコンポーネントをアタッチします
        /// </summary>
        /// <remarks>
        /// ノードに種々のコンポーネントをアタッチすることで、
        /// このノードはいろいろな機能を持つようになります。
        /// コンポーネントは同時に1つのノードにのみアタッチ可能です｡
        /// 既にアタッチされているコンポーネントと同じ型のコンポーネントをアタッチすることはできません。
        /// </remarks>
        /// <typeparam name="TComp">コンポーネント型</typeparam>
        /// <param name="comp">追加したいコンポーネント</param>
        public void Attach<TComp> (TComp comp) where TComp : Component {
            if (comp == null) {
                throw new ArgumentNullException ("Component is null");
            }
            if (comp.Node != null) {
                throw new ArgumentException ("This component already has node");
            }
            if (components.Any (x => x.GetType () == comp.GetType ())) {
                throw new ArgumentException ("This node has same type of component");
            }

            comp.SetNode ((Node)this);
            this.components.Add (comp);
        }


        /// <summary>
        /// このノードからコンポーネントを削除します
        /// </summary>
        /// <remarks>
        /// コンポーネントがnullまたはこのノードにアタッチされていなかった場合は単にこれを無視します。
        /// </remarks>
        /// <param name="comp">削除したいコンポーネント</param>
        /// <returns>削除されたらtrue,そうでなければfalse</returns>
        public bool Detach (Component comp) {
            if (comp == null || !components.Contains (comp)) {
                return false;
            }

            comp.SetNode (null);
            return components.Remove (comp);
        }

        /// <summary>
        /// 指定のインデックスのコンポーネントを取得します
        /// </summary>
        /// <param name="index">コンポーネント番号</param>
        /// <returns>コンポーネント</returns>
        public Component GetComponent (int index) {
            if (index < 0 || index >= ComponentCount) {
                throw new IndexOutOfRangeException ("Index is out of ragne");
            }
            return components[index];
        }

        /// <summary>
        /// アタッチされたコンポーネントのうち指定の型のものを返します
        /// </summary>
        /// <remarks>
        /// このノードにアタッチされているコンポーネントのうち指定の型のものを返します。
        /// このメソッドは<see cref="As{TComp}"/>の別名です。
        /// 原則同じ型のコンポーネントはアタッチできない仕様なので戻り値は１つに決定されます。
        /// <typeparamref name="TComp"/>に基底クラスを指定した場合は一致するどれか１つのコンポーネントが返されます。
        /// </remarks>
        /// <typeparam name="TComp">探しているコンポーネント型</typeparam>
        /// <returns>発見したコンポーネント</returns>
        public TComp GetComponent<TComp> () where TComp : Component {
            return As<TComp> ();
        }


        /// <summary>
        /// アタッチされた全コンポーネントについて指定のメソッドを(あれば)呼び出します
        /// </summary>
        /// <remarks>
        /// このノードにアタッチされた全コンポーネントについて指定のメソッド(引数なし)を持っていればそれ呼び出します。
        /// このメソッドはリフレクションを使って実装されていて比較的遅い関数です。
        /// </remarks>
        /// <param name="name">呼び出したいメソッド名</param>
        public void RecvMessage (string name) {
            if (name == null || name == "") {
                throw new ArgumentNullException ("Function is null");
            }

            foreach (var comp in components) {
                Type t = comp.GetType ();
                var methInfo = t.GetMethod (name);
                if (methInfo != null) {
                    methInfo.Invoke (comp, null);
                }
            }
        }

        /// <summary>
        /// このノードにアタッチされた全コンポーネントについて指定のメソッドを(あれば)呼び出します
        /// </summary>
        /// <remarks>
        /// このノードにアタッチされた全コンポーネントについて指定の関数を持っていればそれを呼び出します。
        /// このメソッドはリフレクションを使って実装されていて比較的遅い関数です。
        /// </remarks>
        /// <typeparam name="TArg1">第1引数の型</typeparam>
        /// <param name="name">関数名</param>
        /// <param name="arg1">第1引数</param>
        public void RecvMessage<TArg1> (string name, TArg1 arg1) {
            if (name == null || name == "") {
                throw new ArgumentNullException ("Function is null");
            }

            foreach (var comp in components) {
                Type t = comp.GetType ();
                var m = t.GetMethod (name);
                if (m != null) {
                    var args = new object[] { arg1 };
                    m.Invoke (comp, args);
                }
            }
        }

        /// <summary>
        /// このノードにアタッチされた全コンポーネントのうち、
        /// 指定の型のコンポーネントの指定のメソッドを(あれば)呼び出します
        /// </summary>
        /// <remarks>
        /// このメソッドは明示的にコンポーネント型を指定して呼び出す所が<see cref="RecvMessage"/>と異なります。
        /// 特定のコンポーネント型のみ呼び出したい場合はこちらの方が高速です。
        /// 型が一致するコンポーネントが複数ある場合は全て呼び出されます。
        /// </remarks>
        /// <typeparam name="TComp">コンポーネントの型</typeparam>
        /// <param name="name">関数名</param>
        public void RecvMessage<TComp> (string name) where TComp : Component {
            if (name == null || name == "") {
                throw new ArgumentNullException ("Function is null");
            }

            foreach (var comp in components.Where (c => c is TComp)) {
                Type t = comp.GetType ();
                var m = t.GetMethod (name);
                if (m != null) {
                    m.Invoke (comp, null);
                }
            }
        }

        /// <summary>
        /// このノードにアタッチされた全コンポーネントのうち、
        /// 指定の型のコンポーネントの指定のメソッドを(あれば)呼び出します
        /// </summary>
        /// <remarks>
        /// このメソッドは明示的にコンポーネント型を指定して呼び出す所が<see cref="RecvMessage"/>と異なります。
        /// 型が一致するコンポーネントが複数ある場合は全て呼び出されます。
        /// </remarks>
        /// <typeparam name="TComp">コンポーネントの型</typeparam>
        /// <typeparam name="TArg1">第1引数の型</typeparam>
        /// <param name="name">関数名</param>
        /// <param name="arg1">第1引数</param>
        public void RecvMessage<TComp, TArg1> (string name, TArg1 arg1) where TComp : Component {
            if (name == null || name == "") {
                throw new ArgumentNullException ("Function is null");
            }

            foreach (var comp in components.Where (c => c is TComp)) {
                Type t = comp.GetType ();
                var m = t.GetMethod (name);
                if (m != null) {
                    var args = new object[] { arg1 };
                    m.Invoke (comp, args);
                }
            }
        }

        /// <summary>
        /// このﾉｰﾄﾞを指定のｺﾝﾎﾟｰﾈﾝﾄ型にキャスト可能な事を確認します
        /// </summary>
        /// <remarks>
        /// このノードに指定の型のコンポーネントがアタッチされていればtrueを返します。
        /// なければfalseを返します。
        /// </remarks>
        /// <typeparam name="TComp">ｺﾝﾎﾟｰﾈﾝﾄの型</typeparam>
        /// <returns>ｱﾀｯﾁされていればtrue, そうでなければfalse</returns>
        public bool Is<TComp> () where TComp : Component {
            return components.Any (cmp => cmp is TComp);
        }


        /// <summary>
        /// このﾉｰﾄﾞを指定のｺﾝﾎﾟｰﾈﾝﾄ型にキャストします
        /// </summary>
        /// <remarks>
        /// このノードに指定の型のコンポーネントがアタッチされていればそれを返します。
        /// なければnullを返します。
        /// 基本的に同型のコンポーネントはアタッチできないので型が決まればインスタンスは一意に決まります。
        /// ただし基底クラスを指定した場合は複数コンポーネントが該当しどのコンポーネントが返るかは未定義です。
        /// </remarks>
        /// <typeparam name="TComp">ｺﾝﾎﾟｰﾈﾝﾄの型</typeparam>
        /// <returns>ｱﾀｯﾁされていればそのコンポーネント、そうでなければnull</returns>
        public TComp As<TComp> () where TComp : Component {
            return (TComp)components.FirstOrDefault (cmp => cmp is TComp);
        }

        /// <summary>
        /// ノードのアップデート処理を行います
        /// </summary>
        /// <remarks>
        /// このノードにアタッチされた全コンポーネントの"GetComponent.OnUpdate"仮想関数を呼び出します。
        /// アップデート順はシーン構造とは関係なく<see cref="ExecutionOrder"/>の順番です。
        /// </remarks>
        /// <param name="time">時刻(グローバル)</param>
        public void Update (int time) {
            foreach (var cmp in components) {
                cmp.OnUpdate (time);
            }
        }

        /// <summary>
        /// このノードのLOD選択処理を行います
        /// </summary>
        /// <remarks>
        /// このノードに<see cref="LODSelector"/>がアタッチされている場合LOD処理を行います。
        /// このノードのみに作用します
        /// </remarks>
        public void Select () {
            foreach (var s in components.Where (cmp => cmp is LODSelector)) {
                ((LODSelector)s).OnSelect ();
            }
        }

        /// <summary>
        /// このノードのリフレッシュ処理を行います
        /// </summary>
        /// <remarks>
        /// このノードとアタッチされたコンポーネントの全てのUniform変数の値を更新します。
        /// このノードにアタッチされた全コンポーネントの"GetComponent.OnReflesh"仮想関数を呼び出します。
        /// </remarks>
        public void Reflesh () {
            // Referencesの方か？
            foreach (var s in components) {
                s.OnReflesh ();
            }
        }

        /// <summary>
        /// 現在のオブジェクトの状態を表す文字列を返します。
        /// </summary>
        public override string ToString () {
            return string.Format ("Attacable: Components={0}, Exec={1}, Scope={2}, Alpha={3}, Rendering={4}, Picking={5}, Collision={6}, LOD={7}",
                ComponentCount, Scope, Alpha, IsRenderingEnabled, IsPickingEnabled, IsCollisionEnabled, IsLODEnabled);
        }
        #endregion
    }
}
